#include <iostream>
#include <cstdio>
#include "asio.hpp"

using asio::ip::tcp;

class Session : public std::enable_shared_from_this<Session>
{
public:
    enum
    {
        E_SESSION_BUFFER_LEN = 2014,
    };
public:
    Session(tcp::socket socket) : socket_(std::move(socket))
    {
        //fprintf(stdout, "session established, %s:%d<-->%s:%d\n",
        //        socket_.local_endpoint().address().to_string().c_str(),
        //        socket_.local_endpoint().port(),
        //        socket_.remote_endpoint().address().to_string().c_str(),
        //        socket_.remote_endpoint().port());
        ++session_count;
    }
    ~Session()
    {
        //fprintf(stdout, "session closed, %s:%d<-->%s:%d\n",
        //        socket_.local_endpoint().address().to_string().c_str(),
        //        socket_.local_endpoint().port(),
        //        socket_.remote_endpoint().address().to_string().c_str(),
        //        socket_.remote_endpoint().port());
        --session_count;
    }
    void Start()
    {
        DoRead();
    }
private:
    void DoRead()
    {
        auto self(shared_from_this());
        socket_.async_read_some(asio::buffer(buffer_, E_SESSION_BUFFER_LEN),
                                [this, self](std::error_code ec, std::size_t length)
        {
            if(ec)
            {
                if(ec != asio::error::eof)
                {
                    fprintf(stdout, "DoRead Error: %s\n", ec.message().c_str());
                }
            }
            else
            {
                DoWrite(length);
            }
        });
    }
    void DoWrite(std::size_t length)
    {
        auto self(shared_from_this());
        asio::async_write(socket_, asio::buffer(buffer_, length),
                          [this, self](std::error_code ec, std::size_t /*length*/)
        {
            if(ec)
            {
                fprintf(stdout, "DoWrite Error: %s\n", ec.message().c_str());
            }
            else
            {
                DoRead();
            }
        });
    }
private:
    tcp::socket socket_;
    std::array<char, E_SESSION_BUFFER_LEN> buffer_;
    static std::size_t session_count;
};

std::size_t Session::session_count = 0;

class Server
{
public:
    Server(asio::io_context& io_context, short port)
        : acceptor_(io_context, tcp::endpoint(tcp::v4(), port))
    {
        DoAccept();
    }
private:
    void DoAccept()
    {
        acceptor_.async_accept([this](std::error_code ec, tcp::socket socket)
        {
            if(ec)
            {
                std::cout << ec.message() << std::endl;
            }
            else
            {
                std::make_shared<Session>(std::move(socket))->Start();
                DoAccept();
            }
        });
    }
private:
    tcp::acceptor acceptor_;
};

int main(int argc, char* argv[])
{
    short port = 8888;
    if(argc > 2) {port = std::atoi(argv[1]);}

    asio::io_context io_context(1);
    Server s(io_context, port);
    io_context.run();
    return 0;
}
